using System;
using System.Collections.Generic;
using Drawing = System.Drawing;
using System.Text.RegularExpressions;

using Nairo.Parser.Dom;

namespace Nairo.Parser
{
	public class CSSParser
	{
		public const string Version = "CSS 1.0";
		string data;
		FileUnit file;
		
		public CSSParser(FileUnit file, string data)
		{
			this.data = data;
			 
			CompressData();
			
			this.file = file;
		}
		
		public CSSParser(FileUnit file)
		{
			this.file = file;
			
			INode[] styleTags = this.file.Head.GetTags("style");
			
			this.data = " ";
			
			foreach (INode tag in styleTags) {
				if (tag is HtmlTag) {
					string innerText = (tag as HtmlTag).InnerText;
					
					if (innerText != null)
						this.data += innerText;
				}
			}
			
			CompressData();
		}
		
		private void CompressData()
		{
			for (int i = 0; i < 32; i++) {
				this.data = this.data.Replace((char)i, ' ');
			}
			
			while (this.data.Contains("  "))
				this.data = this.data.Replace("  ", " ");
		}
		
		public void Parse()
		{
			List<Token> tokens = GetTokens(this.data);
			StyleCollection cssData = new StyleCollection();
			Token head = null;
			Token body = null;
			
			for (int i = 0; i < tokens.Count; i++) {
				if (!IsWhiteSpace(tokens[i])) {
					tokens[i] = Trim(tokens[i]);
					if (IsComment(tokens[i])) {
						cssData.Add(new Comment(this.file, tokens[i].TokenText));
					} else {
						Token t = GetFullToken(ref i, tokens);
						if (t.TokenText.StartsWith("{") && t.TokenText.EndsWith("}")) {
							body = t;
						} else {
							head = t;
						}
						if (body != null && head != null) {
							cssData.Add(ParseCSSDefinition(head, body));
							
							body = head = null;
						}
						Console.WriteLine("'" + t.TokenText + "'");
					}
				}
			}
			
			this.file.Styles = cssData;
		}
		
		public static void Parse(FileUnit file) {
			CSSParser p = new CSSParser(file);
			p.Parse();
		}
		
		private AbstractCSSElement ParseCSSDefinition(Token head, Token body)
		{
			AbstractCSSElement def;
			if (head.TokenText == "*") {
				def = new UniversalSelector(this.file);
			} else if (head.TokenText.Contains("#")) {
				string[] defData = head.TokenText.Split('#');
				def = new IDSelector(defData[1], defData[0], this.file);
			} else if (head.TokenText.StartsWith(".")) {
				throw new NotImplementedException();
			} else if (head.TokenText.Contains(".")) {
				throw new NotImplementedException();
			} else {
				def = new TypeSelector(head.TokenText, this.file);
			}
			
			string[] attributeData = (body.TokenText.Trim('{', '}', ' ', '\t')).Split(new char[] {';'}, StringSplitOptions.RemoveEmptyEntries);
			
			foreach (string attribute in attributeData) {
				string[] pair = attribute.Split(':');
				def.SetAttribute(Trim(pair[0]), ParseCSSValue(pair[0].Trim('{', '}', ' ', '\t'), pair[1].Trim('{', '}', ' ', '\t')));
			}
			
			return def;
		}
		
		private string Trim(string data)
		{
			return data.Trim('{', '}', ' ', '\t');
		}
		
		private object ParseCSSValue(string name, string data)
		{
			name = Trim(name);
			data = Trim(data);
			
			switch (name.ToLower()) {
//					 Font properties
				case "font":
					return CSSPropertyParser.ParseFont(data);
				case "font-family":
					return CSSPropertyParser.ParseFontFamily(data);
				case "font-style":
					return CSSPropertyParser.ParseFontStyle(data);
				case "font-variant":
					return CSSPropertyParser.ParseFontVariant(data);
				case "font-weight":
					return CSSPropertyParser.ParseFontWeight(data);
				case "font-size":
					return CSSPropertyParser.ParseFontSize(data);
//					 Text properties
				case "line-height":
					return CSSPropertyParser.ParseLineHeight(data);
//					 MarginPaddingSize properties
				case "margin":
				case "padding":
				case "border-width":
					return CSSPropertyParser.ParseMarginOrPadding(data);
//					 Border properties
				case "border-style":
					return CSSPropertyParser.ParseBorderStyle(data);
				case "border":
				case "border-left":
				case "border-right":
				case "border-top":
				case "border-bottom":
					return CSSPropertyParser.ParseBorder(data);
//					 Color properties
				case "color":
				case "background-color":
				case "border-color":
				case "border-color-left":
				case "border-color-right":
				case "border-color-top":
				case "border-color-bottom":
					return ConversionHelper.ConvertToColor(data);
//					 Positioning properties
				case "width":
				case "height":
					return CSSPropertyParser.ParseSize(data);
				case "float":
				case "clear":
					return CSSPropertyParser.ParseFloatStyle(data);
				case "display":
					return CSSPropertyParser.ParseDisplayStyle(data);
				default:
					throw new ParseException("Unknown CSS property '" + name + "'!");
			}
		}
		
		private Token GetFullToken(ref int startIndex, List<Token> tokens)
		{
//			 Get first item.
			Token token = tokens[startIndex];
			
			if ((token.TokenText.StartsWith("{") && !token.TokenText.EndsWith("}"))) {
				startIndex++;
				while ((startIndex < tokens.Count - 1) && (!tokens[startIndex].TokenText.StartsWith("{") && !tokens[startIndex].TokenText.EndsWith("}"))) {
					if (!IsComment(tokens[startIndex])) {
						token.TokenText += tokens[startIndex].TokenText;
						token.End = tokens[startIndex].End;
					}
					startIndex++;
				}
				
				if ((startIndex < tokens.Count - 1) && (tokens[startIndex].TokenText.EndsWith("}"))) {
					token.TokenText += tokens[startIndex].TokenText;
					token.End = tokens[startIndex].End;
				} else
					startIndex--;
			} else {
//				 TODO : Implement this, for definition heads with comments in-between!
//				if ((!(token.TokenText.StartsWith("{") && token.TokenText.EndsWith("}")))) {
//					startIndex++;
//					while ((startIndex < tokens.Count - 1) && (!(tokens[startIndex].TokenText.StartsWith("{") && tokens[startIndex].TokenText.EndsWith("}"))) && !IsComment(tokens[startIndex])) {
//						token.TokenText += tokens[startIndex].TokenText;
//						token.End = tokens[startIndex].End;
//						startIndex++;
//					}
//					startIndex--;
//				}
			}
			
			return token;
		}
		
		private bool IsComment(Token t)
		{
			return t.TokenText.StartsWith("*") && t.TokenText.EndsWith("*");
		}
		
		private Token Trim(Token t)
		{
			t.TokenText = t.TokenText.Trim();
			return t;
		}
		
		private bool IsWhiteSpace(Token token)
		{
			foreach (char c in token.TokenText) {
				if (!char.IsWhiteSpace(c))
					return false;
			}
			
			return true;
		}

		private List<Token> GetTokens(string text)
		{
			List<Token> tokens = new List<Token>();
			System.Text.StringBuilder buffer = new System.Text.StringBuilder();
			bool inComment = false;
			bool inDefBrace = false;
			int start = -1;
			
			for (int pos = 0; pos < text.Length; pos++) {
				char ch = this.data[pos];
				switch (ch) {
					case '/':
//						 Kommentar ende?
						if ((pos > 0) && (this.data[pos - 1] == '*')) {
							buffer.Append(ch);
							tokens.Add(new Token(buffer.ToString(), start, pos));
							buffer = new System.Text.StringBuilder();
							start = pos;
							inComment = false;
						} else {
//							 Kommentar start?
							if (!inComment && (pos < this.data.Length - 1) && (this.data[pos + 1] == '*')) {
								inComment = true;
								tokens.Add(new Token(buffer.ToString(), start, pos));
								buffer = new System.Text.StringBuilder();
								start = pos;
								buffer.Append(ch);
							} else {
								buffer.Append(ch);
							}
						}
						break;
					case '{':
						if (!inDefBrace) {
							inDefBrace = true;
							tokens.Add(new Token(buffer.ToString(), start, pos));
							buffer = new System.Text.StringBuilder();
							buffer.Append(ch);
							start = pos;
						}
						break;
					case '}':
						if (inDefBrace) {
							inDefBrace = false;
							buffer.Append(ch);
							tokens.Add(new Token(buffer.ToString(), start, pos));
							buffer = new System.Text.StringBuilder();
							start = pos;
						}
						break;
					default:
						if (start == -1) {
							start = pos;
						}
						buffer.Append(ch);
						break;
				}
			}
			
			return tokens;
		}
	}
	
	public static class ConversionHelper
	{
		private static Dictionary<string, Drawing.Color> colors;
		
		private static void Initialize()
		{
			if (colors == null) {
				colors = new Dictionary<string, Drawing.Color>();
				colors.Add("aqua", FromHexString("#00FFFF"));
				colors.Add("black", FromHexString("#000000"));
				colors.Add("blue", FromHexString("#0000FF"));
				colors.Add("fuchsia", FromHexString("#FF00FF"));
				colors.Add("gray", FromHexString("#808080"));
				colors.Add("green", FromHexString("#008000"));
				colors.Add("lime", FromHexString("#00FF00"));
				colors.Add("maroon", FromHexString("#800000"));
				colors.Add("navy", FromHexString("#000080"));
				colors.Add("olive", FromHexString("#808000"));
				colors.Add("orange", FromHexString("#ffA500"));
				colors.Add("purple", FromHexString("#800080"));
				colors.Add("red", FromHexString("#FF0000"));
				colors.Add("silver", FromHexString("#C0C0C0"));
				colors.Add("teal", FromHexString("#008080"));
				colors.Add("white", FromHexString("#FFFFFF"));
				colors.Add("yellow", FromHexString("#FFFF00"));
			}
		}
		
		public static double ParseSize(string data)
		{
			return 0;
		}
		
		public static Drawing.Color ConvertToColor(string data)
		{
			Initialize();
			if ((data.Length == 7) || (data[0] == '#')) {
				return FromHexString(data);
			} else {
				Drawing.Color value = Drawing.Color.Empty;
				if (colors.TryGetValue(data.ToLower(), out value)) {
					return value;
				} else {
					if (data.ToLower().StartsWith("rgb(") && data.ToLower().EndsWith(")")) {
						string[] values = data.Trim('r', 'g', 'b', '(', ')').Split(',');
						if (values.Length == 3) {
							return Drawing.Color.FromArgb((int)ParseNumber(values[0], 255), (int)ParseNumber(values[1], 255), (int)ParseNumber(values[2], 255));
						} else
							throw new ParseException("unkown color '" + data + "'!");
					} else {
						throw new ParseException("unknown color '" + data + "'!");
					}
				}
			}
		}
		
		public static float ParseNumber(string data, float maxValue)
		{
			if (data.Contains("%")) {
				data = data.Replace("%", "");
				return (float)(maxValue * (float.Parse(data) / 100));
			}
			
			return float.Parse(data);
		}
		
		public static Drawing.Color FromHexString(string data)
		{
			if (!((data.Length == 7) || (data.Length == 4)) || (data[0] != '#'))
				throw new FormatException("'" + data + "' is not a valid hex color!");
			
			string hex = data.Substring(1);
			int red = 0, green = 0, blue = 0;
			
			try {
				if (hex.Length == 3) {
					red = Convert.ToInt32(hex[0].ToString(), 16);
					green = Convert.ToInt32(hex[1].ToString(), 16);
					blue = Convert.ToInt32(hex[2].ToString(), 16);
				} else {
					red = Convert.ToInt32(hex.Substring(0, 2), 16);
					green = Convert.ToInt32(hex.Substring(2, 2), 16);
					blue = Convert.ToInt32(hex.Substring(4, 2), 16);
				}
			} catch (FormatException) {
				throw new FormatException("'" + data + "' is not a valid hex color!");
			}
			
			return Drawing.Color.FromArgb(red, green, blue);
		}
	}	
}
